package nioTest.com.io;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * @author : yang.xp
 * @date : 2021/8/23 14:21
 * @description :
 */
public class NIOServer {

    private int flag = 1;

    private int blockSize = 4096;

    // 因为while 是单线程的，所有没有并发问题
    private ByteBuffer sendBuffer = ByteBuffer.allocate(blockSize);

    private ByteBuffer receiveBuffer = ByteBuffer.allocate(blockSize);

    // 选择器
    private Selector selector;

    public NIOServer(int port) throws IOException {

        // 通过底层Native 方法 Net.socket0创建一个channel。
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 调用底层Native的配置阻塞 IOUtil.configureBlocking
        serverSocketChannel.configureBlocking(false);
        // 通过channel创建一个 ServerSocket 使用了 ServerSocketAdaptor 适配器。主要使用 SocketImpl
        ServerSocket serverSocket = serverSocketChannel.socket();
        // 服务端 通过Native SocketImpl 绑定端口，操作底层绑定指定端口 java.net.DualStackPlainSocketImpl.bind0
        serverSocket.bind(new InetSocketAddress(port));
        // 打开选择器 WindowsSelectorImpl
        selector = Selector.open();
        // 把channel注册到指定的选择器上，并且指定channel感兴趣的事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("Server start->" + port);

    }

    // /监听事件
    public void listen() throws IOException {
        while (true) {
            selector.select();
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = null;
                selectionKey = iterator.next();
                iterator.remove();
                /* 业务逻辑 */
                handleKey(selectionKey);
                selector.select();
            }

        }
    }

    /* 处理可以 */
    public void handleKey(SelectionKey selectionKey) throws IOException {
        ServerSocketChannel serverSocketChannel = null;
        SocketChannel socketChannel = null;
        String receiveText;
        String sendText;

        int count = 0;
        if (selectionKey.isAcceptable()) {
            // 本来accept是阻塞的，但是这个selectKey是已经accept的，所里会立刻执行
            serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
            // 每来一个客户端都会有一个channel与他对应
            socketChannel = serverSocketChannel.accept();
            socketChannel.configureBlocking(false);
            socketChannel.register(selector, SelectionKey.OP_READ);

        } else if (selectionKey.isReadable()) {
            receiveBuffer.clear();
            socketChannel = (SocketChannel) selectionKey.channel();
            try {
                count = socketChannel.read(receiveBuffer);
            } catch (IOException e) {
                System.out.println(socketChannel.getRemoteAddress() + " 断开连接");
                socketChannel.close();
                return;
            }
            if (count > 0) {
                receiveText = new String(receiveBuffer.array(), 0, count);
                System.out.println("服务端接受到客户端的信息：" + receiveText);
                socketChannel.register(selector, SelectionKey.OP_WRITE);
            }
        } else if (selectionKey.isWritable()) {
            sendBuffer.clear();
            socketChannel = (SocketChannel) selectionKey.channel();
            /* 发送的数据 */
            sendText = "msd send to the client." + flag++;
            sendBuffer.put(sendText.getBytes());
            sendBuffer.flip();
            /* 发送至服务器 */
            socketChannel.write(sendBuffer);
            System.out.println("服务端发送给客户端：" + sendText);
            socketChannel.register(selector, SelectionKey.OP_READ);
        }

    }

    public static void main(String[] args) throws IOException {
        int port = 10280;
        NIOServer nioServer = new NIOServer(port);
        nioServer.listen();

    }
}
